#include<stdio.h>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
#include<string.h>
#include<assert.h>
#include<stdlib.h>

#define NUM 1024

#define NONE_FLUSH 0x0   //无缓冲（立即刷新）
#define LINE_FLUSH 0x1   //行缓冲
#define FULL_FLUSH 0x2   //全缓冲

//类型重命名
typedef struct _MyFILE
{
    int  _fileno;
    char _buffer[NUM];
    int  _end;      //_buffer的结尾，后面用到
    int  _flags;   //fflush method
}MyFILE;

MyFILE* my_fopen(const char* filename, const char* method)
{
    assert(filename);
    assert(method);

    int flags = O_RDONLY;  //默认以写、读方式打开

    if (strcmp(method, "r") == 0)
    {}
    else if (strcmp(method, "r+") == 0)
    {}
    else if (strcmp(method, "w") == 0)
    {
        //如果是以写方式打开，就修改为O_WRONLY。
        //如果打开的时候不存在，就O_CREAT 创建
        //如果打开需要将文件清空，就O_TRUNC
        flags = O_WRONLY | O_CREAT | O_TRUNC;
    }
    else  if (strcmp(method, "w+") == 0)
    {}
    else if (strcmp(method, "a") == 0)
    {
        //追加同理
        flags = O_WRONLY | O_CREAT | O_APPEND;
    }
    else if (strcmp(method, "a+") == 0)
    {}

    //打开文件
    int fileno = open(filename, flags, 0666);
    if (fileno < 0)
    {
        return NULL;
    }
    //打开文件成功就申请空间
    MyFILE* fp = (MyFILE*)malloc(sizeof(MyFILE));
    if (fp == NULL)  return fp;
    memset(fp, 0, sizeof(MyFILE));

    //1.正常情况下，会在打开文件时进行判断，使用的系统接口为stat
    //man 2 stat。stat就是用来检测一个特定路径下，文件是否存在
    //这里没有使用，只进行简单演示
    //2.一般情况下，我们打开的文件是什么类型，也要在my_fopen里获得
    //这里没有使用处理，
    //3.这里默认将刷新方式设置为行缓冲

    fp->_fileno = fileno;
    fp->_flags |= LINE_FLUSH;
    fp->_end = 0;
    return fp;
}
void my_fflush(MyFILE* fp)
{
    assert(fp);
    if (fp->_end > 0)
    {
        write(fp->_fileno, fp->_buffer, fp->_end);
        fp->_end = 0;
        syncfs(fp->_fileno);  //man 2 sync：把buffer cache直接提交到磁盘 
    }
}
//这里由于是模拟实现，就不考虑my_fwrite的返回值
void my_fwrite(MyFILE* fp, const char* start, int len)
{
    // start表示要写入的字符串，len表示要写多长
    assert(fp);
    assert(start);
    assert(len > 0);

    //写入到缓冲区
    strncpy(fp->_buffer + fp->_end, start, len);  //将数据写到缓冲区了
    fp->_end += len;
    //_end永远指向的是有效字符的下一个位置
    if (fp->_flags & NONE_FLUSH)
    {}
    else if (fp->_flags & LINE_FLUSH)
    {
        if (fp->_end > 0 && fp->_buffer[fp->_end - 1] == '\n')
        {
            //仅仅是写到内核中
            write(fp->_fileno, fp->_buffer, fp->_end);
            fp->_end = 0;
            syncfs(fp->_fileno);
        }
    }
    else if (fp->_flags & FULL_FLUSH)
    {}
}

void my_fclose(MyFILE* fp)
{
    //如果在close时，还有数据，那就刷新
    my_fflush(fp);
    close(fp->_fileno);
    free(fp);
}
int main()
{
    MyFILE* fp = my_fopen("log.txt", "w");
    if (fp == NULL)
    {
        printf("my_fopen error\n");
        return 1;
    }

    //const char* s1 = "this is a testAAAA\n";
    //my_fwrite(fp, s1, strlen(s1));
    //printf("消息立即刷新！\n");
    //sleep(3);

    //const char* s2 = "this is a testBBBB";
    //my_fwrite(fp, s2, strlen(s2));   
    ////不带'\n'，如果继续写，那不会写到文件里，而是放到缓冲区里暂存 
    //printf("写入了一个不满足刷新条件的字符串!\n");
    //sleep(3);


    //const char* s3 = "this is a testCCCC";
    //my_fwrite(fp, s3, strlen(s3));
    //printf("写入了一个不满足刷新条件的字符串!\n");
    //sleep(3);


    //const char* s4 = "end\n";
    //my_fwrite(fp, s4, strlen(s4));
    //printf("写入了一个满足刷新条件的字符串！\n");
    //sleep(3);


    //const char* s5 = "PPPPPPPPP";
    //my_fwrite(fp, s5, strlen(s5));
    //printf("写入了一个不满足刷新条件的字符串!\n");
    //sleep(1);
    //my_fflush(fp);
    //sleep(3);

    const char* s5 = "PPPPPPPPP";
    my_fwrite(fp, s5, strlen(s5));
    printf("写入了一个不满足刷新条件的字符串!\n");
    
    //my_fclose(fp);
    fork();
    //模拟进程退出
    my_fclose(fp);
}

